/**
 * 
 */
package tool.dialog;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Constructor;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;

import annotations.MainConstructor;

import tool.CustomizedButton;
import tool.MainConstructorNotFoundException;
import tool.classloader.ClassNotSupportedException;


/**rappresenta il Dialog che compare quando una nuova classe di MapObject/MapLayer
 * deve essere costruita. L'editor in sé non conosce ogni ForeGround che l'utente
 * può inserire; quindi non conosce il costruttore da usare quando l'utente seleziona
 * un ForeGround per inserirlo nella mappa. In linea teorica potrebbe utilizzare
 * sempre il costruttore senza parametri (attraverso <tt>newInstance()</tt>), tuttavia
 * può darsi che una classe non ne possegga uno. In questi casi è necessario
 * ricorrere ad un costruttore con parametri. Affinché l'oggetto possa
 * essere costruito è necessario che l'utente scriva i parametri da inserire nel
 * costruttore. Questa classe si preoccupa di gestire il form in cui
 * l'utente scriverà tali parametri!
 * <p>La classe utilizza (per istanziare la classe {@link #toConstruct}) uno solo dei tanti
 * possibili costruttori che la classe possiede. Questo costruttore è definito <strong>principale</strong>
 * ed è diversificato dagli altri attraverso l'uso dell'annotazione <tt>MainConstructor</tt>;
 * tra le altre cose, l'annotazione comprende anche altre informazioni: le descrizioni dei parametri
 * vengono effettivamente usate dal Dialog inserendole in appositi JLabel; per quanto invece riguarda
 * i tipi essi non vengono esplicitamente utilizzati. 
 * 
 * <p>Questo oggetto dovrebbe essere invocato solamente se la classe da istanziare non possiede
 * un costruttore vuoto; per questo essa non utilizza in alcun modo l'annotazione <tt>EmptyConstructorOwner</tt>
 * 
 * <p>Non tutte le classi possono essere costruite da questo JDialog: per scelta di progetto, solo classi
 * con costruttore principale avente come tipi di parametri parametri primitivi o stringhe possono essere
 * correttamente usati; per gli altri il Dialog spedisce un messaggio di errore
 * 
 * <p>Il compito della classe è assicurare i parametri per l'inizializzazione della nuova classe: non spetta cioè
 * a JCOnstructorDialog creare in sé la nuova istanza. I parametri da utilizzare per la costruzione posson
 * essere ricavati dal metodo {@link #getParameterlist()}
 * 
 * @author Koldar
 * @version 1.7
 */
public class JConstructorDialog extends JDialog implements ActionListener{

	private static final long serialVersionUID = -5666417425248711394L;

	/** Dimensioni della finestra di dialogo. */
	private static final Dimension DIALOG_SIZE = new Dimension(400, 200);
	/** Dimensioni preferite dei pulsanti. */
	private static final Dimension BUTTON_PREFERRED_SIZE = new Dimension(100, 40);
	
	// scritte sui pulsanti.
	private static final String TXT_OK = "Ok";
	private static final String TXT_CANCEL = "Cancel";
	private static final String SPACER = "   ";
	
	/**rappresenta la classe che deve essere costruita dall'utente*/
	private Class<?> toConstruct;
	/**rappresenta il costruttore principale da utilizzare per costruire la nuova classe*/
	private Constructor<?> mainConstructor;
	/**rappresenta il supporto del GridBagLayout del componente*/
	private GridBagConstraints layoutInfo;
	/**rappresenta il pulsante di ok per confermare i parametri di utilizzo per costruire la nuova classe*/
	private JButton ok;
	/**rappresenta il pulsante di cancel per annullare ogni modifica fatta*/
	private JButton cancel;
	/**rappresenta la lista di pannelli contenenti le descrizioni e le textfield in cui i parametri vanno inseriti*/
	private Vector<JParameterPanel> parameterPanelList;
	/**indica la lista di parametri catturati dal Dialog per costruire il nuovo oggetto*/
	private Object[] parameterlist;
	
	/**costruisce un nuovo Dialog per la costruzione dell'oggetto passato da _toconstruct
	 * 
	 * @param parent indica la finestra possedente questo JDialog 
	 * @param _toconstruct la classe che deve essere istanzializzata
	 * @throws ConstructorDescriptorNotFoundException in caso la classe non possegga un costruttore annotato con MainConstructor
	 */
	public JConstructorDialog(Frame parent,Class<?> _toconstruct) throws MainConstructorNotFoundException{
		this(parent,_toconstruct,true);
	}
	
	/**recupera il {@link #mainConstructor} e popola la lista dei pannelli concetewnti i parametri da settare
	 * 
	 */
	protected void generateViewModel() throws ClassNotSupportedException,NullPointerException,MainConstructorNotFoundException{
		this.mainConstructor=this.retrieveMainConstructor();
		this.populateParameterPanelList();
		this.addComponents();
		this.setVisible(true);
	}
	
	/**costruisce un normale JConstructorDialog solo che popola i parametri da inserire se e solo se generatemodelview è TRUE
	 * 
	 * @param parent
	 * @param toconstruct
	 * @param generatemodelview
	 * @throws MainConstructorNotFoundException
	 */
	public JConstructorDialog(Frame parent,Class<?> toconstruct,boolean generatemodelview) throws MainConstructorNotFoundException{
		super(parent,"Construct a new "+toconstruct.getSimpleName()+" class",true);
		
		this.setLayout(new GridBagLayout());
		
		this.parameterlist = new Object[0];
		this.toConstruct = toconstruct;
		this.parameterPanelList = new Vector<JParameterPanel>(0,1);
		this.ok = new CustomizedButton(JConstructorDialog.TXT_OK,20,Color.WHITE,Color.BLUE,Color.WHITE,Color.BLUE,new Color(50,0,200),Color.GREEN,3,"Britannic Bold",Color.BLUE,Color.WHITE,Color.GREEN);
		this.ok.setPreferredSize(JConstructorDialog.BUTTON_PREFERRED_SIZE);
		this.cancel = new CustomizedButton(JConstructorDialog.TXT_CANCEL,20,Color.WHITE,Color.BLUE,Color.WHITE,Color.BLUE,new Color(50,0,200),Color.GREEN,3,"Britannic Bold",Color.BLUE,Color.WHITE,Color.GREEN);
		this.cancel.setPreferredSize(JConstructorDialog.BUTTON_PREFERRED_SIZE);
		this.layoutInfo = new GridBagConstraints();
		this.setSize(JConstructorDialog.DIALOG_SIZE);
		this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		this.ok.addActionListener(this);
		this.cancel.addActionListener(this);
		
		if (generatemodelview){
			try {
				this.generateViewModel();
			} catch (NullPointerException | ClassNotSupportedException error) {
				new ErrorDialog(parent,error.getMessage());
				this.dispose();
			}
		}
	}
	
	/**
	 * @return the toConstruct
	 */
	public Class<?> getToConstruct() {
		return toConstruct;
	}
	/**oltre ad impostare la semplice variabile {@link #toConstruct}, il metodo raccoglie informazioni su tale nuova classe
	 * per riassettare i pannelli TextField nonche' riassettare le variabili interne
	 * 
	 * @param toConstruct the toConstruct to set
	 * @throws MainConstructorNotFoundException in caso la classe passata non abbia un costruttore annotato con MainConstructor
	 */
	public void setToConstruct(Class<?> toConstruct) throws MainConstructorNotFoundException {
		this.toConstruct = toConstruct;
		
		try {
			this.mainConstructor=this.retrieveMainConstructor();
			this.removeAll();
			this.parameterPanelList.removeAllElements();
			this.populateParameterPanelList();
			this.addComponents();
		} catch (NullPointerException | ClassNotSupportedException error) {
			new ErrorDialog((JFrame)this.getParent(),error.getMessage());
			this.dispose();
			return;
		}
	}
	
	/**aggiunge tutta la parte grafica (pannelli di tipo Parameters <strong>e</strong> pulsanti.
	 * Affinche' il metodo funzioni e' necessario che la lista {@link #parameterPanelList} sia stata popolata
	 * dal metodo {@link #populateParameterPanelList()}
	 */
	protected void addComponents(){
		layoutInfo.gridx = 0;
		layoutInfo.gridy = 0;
		layoutInfo.gridwidth = 2;
		layoutInfo.gridheight = 1;
		layoutInfo.weighty = 0.5;
		layoutInfo.fill = GridBagConstraints.BOTH;
		this.addPanels();
		layoutInfo.fill = GridBagConstraints.NONE;
		layoutInfo.anchor = GridBagConstraints.CENTER;
		this.layoutInfo.weighty = 0.5;
		this.layoutInfo.gridwidth = 1;
		this.add(cancel,this.layoutInfo);
		this.layoutInfo.gridx = 1;
		this.add(ok,this.layoutInfo);
	}



	/**aggiunge i pannelli di rilevamento parametri che si trovano nella {@link #parameterPanelList}; questo
	 * metodo e' meglio richiamarlo dopo aver popolato la lista tramite {@link #populateParameterPanelList()}.
	 * <p>NOTA: addPanels non resetta le informazioni contenute in {@link #layoutInfo}, quindi prima di richiamare questo
	 * metodo e' opportuno resettarle manualmente, altrimenti si corre il rischio agguingere i pannelli in posti non desiderati
	 */
	private void addPanels(){
		this.layoutInfo.weightx = 1;
		this.layoutInfo.weighty = 1;
		for (JParameterPanel panel:this.parameterPanelList){
			this.add(panel,layoutInfo);
			this.layoutInfo.gridy++;
		}
	}



	/**popola la lista contenente i pannelli in cui scrivere i parametri di istanzializzazione
	 * 
	 * @throws ClassNotSupportedException in caso in cui uno dei parametri non sia o un tipo primitivo oppure una Stringa.
	 * in questo caso tutti i pannelli inseriti verranno eliminati ({@link #parameterPanelList} assumer� size nulla)
	 */
	protected void populateParameterPanelList() throws ClassNotSupportedException{
		MainConstructor annotation=this.mainConstructor.getAnnotation(MainConstructor.class);
		Class<?>[] params=this.mainConstructor.getParameterTypes();
		Class<?> param=null;
		for (int i=0;i<params.length;i++){
			param=params[i];
			this.parameterPanelList.add(new JParameterPanel(new JLabel(annotation.descriptions()[i] + JConstructorDialog.SPACER),this.retrieveCorrectTextField(param)));
		}
	}
	/**al variare del tipo di classe param, il metodo ritorna una JTextField adeguata a
	 * contenere tale tipo. Le TextField possibili sono:
	 * <ul>
	 *  <li>JStringField: quando param e' una stringa</li>
	 *  <li>JNumberField: quando param e' un int</li>
	 *  <li>JNumberFIeld: quando param e' un tipo estenso da Number</li>
	 * </ul>
	 * 
	 * @param param la classe che rappresenta i ltipo di dato che la textfield deve percepire dall'utente
	 * @return la JTextField adeguata per il dato che bisogna leggere
	 * @throws ClassNotSupportedException in caso in cui non ci sia alcuna TextField che possa incapsulare il tipo del parametro del costruttore
	 */
	protected JField<?> retrieveCorrectTextField(Class<?> param) throws ClassNotSupportedException{
		if (param==int.class){
			return new JNumberField<Integer>(Integer.class);
		}
		if (param==String.class){
			return new JStringField();
		}
		if ((param.isArray())){
			if (param.getComponentType()==String.class)
				return new JStringArrayField();
			if (param.getComponentType()==Integer.class)
				return new JIntegerArrayField();
		}
		if (param.getSuperclass()==Number.class){//e' un tipo extends di Number generico
			return new JNumberField(param);
		}
		throw new ClassNotSupportedException(param);
	}
	
	/**scorre la classe {@link #toConstruct} per reperire il costruttore da utilizzare.
	 * Per definizione il costruttore (detto <strong>principale</strong>)da utilizzare per costruire la classe e' quello annotato
	 * tramite l'annotazione MainConstructor.
	 * 
	 * @return il costruttore principale 
	 * @throws NullPointerException in caso in cui l'attributo {@link #toConstruct} sia NULL
	 * @throws MainConstructorNotFoundException in caso in cui la classe {@link #toConstruct} sia priva di un costruttore annotato con MainConstructor
	 */
	private Constructor<?> retrieveMainConstructor() throws NullPointerException,MainConstructorNotFoundException{
		if (this.toConstruct==null){
			throw new NullPointerException("toConstruct null!");
		}
		Constructor<?>[] constructors=this.toConstruct.getConstructors();
		MainConstructor annotation=null;
		for (Constructor<?> con:constructors){
			annotation=con.getAnnotation(MainConstructor.class);
			if (annotation!=null){
				return con;
			}
		}
		throw new MainConstructorNotFoundException(this.toConstruct);
	}
	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource()==this.ok){
			this.parameterlist=new Object[this.mainConstructor.getParameterTypes().length];
			for (int i=0;i<this.parameterPanelList.size();i++){
				//i textfield all'interno sono tutte estensioni di JField
				if (((JField<?>)this.parameterPanelList.get(i).getTextfield()).getState()==JField.WRONG){
					new ErrorDialog((JFrame) this.getParent(),"formato parametri invalido!");
					return;
				}else{
					this.parameterlist[i]=((JField<?>)this.parameterPanelList.get(i).getTextfield()).getValue();
				}
			}
			//i parametri sono stati inseriti correttamente
			this.dispose();
		}
		if (e.getSource()==this.cancel){
			this.dispose();
		}
	}
	/**
	 * @return the parameterlist
	 */
	public Object[] getParameterlist() {
		return parameterlist;
	}
	/**
	 * @return the mainConstructor
	 */
	public Constructor<?> getMainConstructor() {
		return mainConstructor;
	}
	/**
	 * @param mainConstructor the mainConstructor to set
	 */
	public void setMainConstructor(Constructor<?> mainConstructor) {
		this.mainConstructor = mainConstructor;
	}
	/**
	 * @return the parameterPanelList. For further information visit {@link #parameterPanelList}
	 */
	protected Vector<JParameterPanel> getParameterPanelList() {
		return parameterPanelList;
	}
	
	
	
	
	
	
}
